﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.IO;
using System.Text;

namespace CrusadeGmTool
{
    /// <summary>
    /// Utility class used to host common functions that do not fit inside a specific object.
    /// </summary>
    public static class Utility
    {
        public static bool IsPowerOf2(int num)
        {
            return (num & (num - 1)) == 0;
        }

        public static bool IsPowerOf2Plus1(int num)
        {
            return IsPowerOf2(num - 1);
        }

        #region Buffer Formatting
        /// <summary>
        /// Formats the buffer to an output to a hex editor view of the data
        /// </summary>
        /// <param name="buffer">The buffer to be formatted</param>
        /// <returns>A System.String containing the formatted buffer</returns>
        public static string FormatBuffer(byte[] buffer)
        {
            if (buffer == null)
                return string.Empty;

            string formatted = FormatBuffer(buffer, buffer.Length);

            return formatted;
        }

        /// <summary>
        /// Formats the buffer to an output to a hex editor view of the data
        /// </summary>
        /// <param name="buffer">The buffer to be formatted</param>
        /// <param name="length">The length in bytes to format</param>
        /// <returns>A System.String containing the formatted buffer</returns>
        public static string FormatBuffer(byte[] buffer, int length)
        {
            if (buffer == null)
                return string.Empty;

            StringBuilder builder = new StringBuilder();
            MemoryStream ms = new MemoryStream(buffer);

            FormatBuffer(builder, ms, length);

            return builder.ToString();
        }

        /// <summary>
        /// Formats the stream buffer to an output to a hex editor view of the data
        /// </summary>
        /// <param name="buffer">The stream to be formatted</param>
        /// <param name="length">The length in bytes to format</param>
        /// <returns>A System.String containing the formatted buffer</returns>
        public static string FormatBuffer(Stream input, int length)
        {
            StringBuilder builder = new StringBuilder();

            FormatBuffer(builder, input, length);

            return builder.ToString();
        }


        /// <summary>
        /// Formats the stream buffer to an output to a hex editor view of the data
        /// </summary>
        /// <param name="builder">The string builder to output the formatted buffer to</param>
        /// <param name="buffer">The stream to be formatted</param>
        /// <param name="length">The length in bytes to format</param>
        public static void FormatBuffer(StringBuilder builder, Stream input, int length)
        {
            length = (int)Math.Min(length, input.Length);
            builder.AppendLine("        0  1  2  3  4  5  6  7   8  9  A  B  C  D  E  F");
            builder.AppendLine("       -- -- -- -- -- -- -- --  -- -- -- -- -- -- -- --");

            int byteIndex = 0;

            int whole = length >> 4;
            int rem = length & 0xF;

            for (int i = 0; i < whole; ++i, byteIndex += 16)
            {
                StringBuilder bytes = new StringBuilder(49);
                StringBuilder chars = new StringBuilder(16);

                for (int j = 0; j < 16; ++j)
                {
                    int c = input.ReadByte();

                    bytes.Append(c.ToString("X2"));

                    if (j != 7)
                    {
                        bytes.Append(' ');
                    }
                    else
                    {
                        bytes.Append("  ");
                    }

                    if (c >= 0x20 && c < 0x80)
                    {
                        chars.Append((char)c);
                    }
                    else
                    {
                        chars.Append('.');
                    }
                }

                builder.Append(byteIndex.ToString("X4"));
                builder.Append("   ");
                builder.Append(bytes.ToString());
                builder.Append("  ");
                builder.AppendLine(chars.ToString());
            }

            if (rem != 0)
            {
                StringBuilder bytes = new StringBuilder(49);
                StringBuilder chars = new StringBuilder(rem);

                for (int j = 0; j < 16; ++j)
                {
                    if (j < rem)
                    {
                        int c = input.ReadByte();

                        bytes.Append(c.ToString("X2"));

                        if (j != 7)
                        {
                            bytes.Append(' ');
                        }
                        else
                        {
                            bytes.Append("  ");
                        }

                        if (c >= 0x20 && c < 0x80)
                        {
                            chars.Append((char)c);
                        }
                        else
                        {
                            chars.Append('.');
                        }
                    }
                    else
                    {
                        bytes.Append("   ");
                    }
                }

                builder.Append(byteIndex.ToString("X4"));
                builder.Append("   ");
                builder.Append(bytes.ToString());
                builder.Append("  ");
                builder.AppendLine(chars.ToString());
            }
        }
        #endregion

        #region Encoding
        private static Encoding utf8, utf8WithEncoding;

        public static Encoding UTF8
        {
            get
            {
                if (utf8 == null)
                    utf8 = new UTF8Encoding(false, false);

                return utf8;
            }
        }

        public static Encoding UTF8WithEncoding
        {
            get
            {
                if (utf8WithEncoding == null)
                    utf8WithEncoding = new UTF8Encoding(true, false);

                return utf8WithEncoding;
            }
        }
        #endregion

        #region Conversion
        public static bool TryConvert<TConverFrom, UConvertTo>(TConverFrom convertFrom, out UConvertTo convertTo)
        {
            convertTo = default(UConvertTo);
            bool converted = false;

            TypeConverter converter = TypeDescriptor.GetConverter(convertFrom);

            if (converter.CanConvertTo(typeof(UConvertTo)))
            {
                convertTo = (UConvertTo)converter.ConvertTo(convertFrom, typeof(UConvertTo));
                converted = true;
            }

            return converted;
        }

        public static bool TryConvert(Type convertFrom, object from, Type convertTo, out object to)
        {
            to = null;
            bool converted = false;

            TypeConverter converter = TypeDescriptor.GetConverter(convertTo);

            if (converter.CanConvertFrom(convertFrom))
            {
                to = converter.ConvertFrom(from);
                converted = true;
            }

            return converted;
        }

        public static TConverFrom ConvertReferenceType<TConverFrom, UConvertTo>(UConvertTo value)
        {
            if (value == null)
            {
                return default(TConverFrom);
            }
            else if (typeof(TConverFrom).IsAssignableFrom(value.GetType()) == true)
            {
                return (TConverFrom)((object)value);
            }

            return default(TConverFrom);
        }

        public static TConverFrom ConvertValueType<TConverFrom, UConvertTo>(UConvertTo value)
        {
            IConvertible convertible = value as IConvertible;

            if (convertible != null)
            {
                return (TConverFrom)System.Convert.ChangeType(convertible, typeof(TConverFrom));
            }

            TypeConverter converter = TypeDescriptor.GetConverter(value);

            if (converter.CanConvertTo(typeof(TConverFrom)))
            {
                return (TConverFrom)converter.ConvertTo(value, typeof(TConverFrom));
            }

            if (value == null)
            {
                throw new InvalidCastException(string.Format(CultureInfo.InvariantCulture,
                    "Unable to cast type '{0}'.", typeof(TConverFrom).Name));
            }

            return default(TConverFrom);
        }

        public static TConverFrom? ConvertNullableType<TConverFrom, UConvertTo>(UConvertTo value) where TConverFrom : struct
        {
            if (value == null)
            {
                return null;
            }

            IConvertible convertible = value as IConvertible;

            if (convertible != null)
            {
                return (TConverFrom)System.Convert.ChangeType(convertible, typeof(TConverFrom));
            }

            TypeConverter converter = TypeDescriptor.GetConverter(value);

            if (converter.CanConvertTo(typeof(TConverFrom)))
            {
                return (TConverFrom)converter.ConvertTo(value, typeof(TConverFrom));
            }

            return new TConverFrom?((TConverFrom)((object)value));
        }
        #endregion

        #region Console Helpers
        private static Stack<ConsoleColor> consoleColors = new Stack<ConsoleColor>();

        /// <summary>
        /// Pushes the color to the console
        /// </summary>
        public static void PushColor(ConsoleColor color)
        {
            try
            {
                consoleColors.Push(Console.ForegroundColor);
                Console.ForegroundColor = color;
            }
            catch
            {
            }
        }

        /// <summary>
        /// Pops the color of the console to the previous value.
        /// </summary>
        public static void PopColor()
        {
            try
            {
                Console.ForegroundColor = consoleColors.Pop();
            }
            catch
            {

            }
        }
        #endregion

        public static void EnsureDirectory(string directory)
        {
            if (!Directory.Exists(directory))
            {
                Directory.CreateDirectory(directory);
            }
        }

        public static int ReadAllBytesFromStream(Stream stream, byte[] buffer)
        {
            int offset = 0;
            int bytesRead;

            while ((bytesRead = stream.Read(buffer, offset, buffer.Length - offset)) > 0)
            {
                offset += bytesRead;
            }

            return offset;
        }

        private static byte[] _64Encode = new byte[] { 
            0x41, 0x42, 0x43, 0x44, 0x45, 70,   0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 80, 
            0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 90,   0x61, 0x62, 0x63, 100,  0x65, 0x66, 
            0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 110,  0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 
            0x77, 120,  0x79, 0x7a, 0x30, 0x31, 50,   0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x2b, 0x2f
         };

        public static bool IsBase64Encoded(byte b)
        {
            for (int i = 0; i < _64Encode.Length; i++)
            {
                if (_64Encode[i].Equals(b))
                {
                    return true;
                }
            }

            return false;
        }

        public static int HighWord(int n)
        {
            return ((n >> 0x10) & 0xffff);
        }

        public static int HighWord(IntPtr n)
        {
            return HighWord((int)((long)n));
        }

        public static int LowWord(int n)
        {
            return (n & 0xffff);
        }

        public static int LowWord(IntPtr n)
        {
            return LowWord((int)((long)n));
        }

        public static int MakeLong(int low, int high)
        {
            return ((high << 0x10) | (low & 0xffff));
        }

        public static IntPtr MakeLParam(int low, int high)
        {
            return (IntPtr)((high << 0x10) | (low & 0xffff));
        }

        public static int SignedHighWord(int n)
        {
            return (short)((n >> 0x10) & 0xffff);
        }

        public static int SignedHighWord(IntPtr n)
        {
            return SignedHighWord((int)((long)n));
        }

        public static int SignedLowWord(int n)
        {
            return (short)(n & 0xffff);
        }

        public static int SignedLowWord(IntPtr n)
        {
            return SignedLowWord((int)((long)n));
        }
    }
}
